package com.ruoyi.common.config.pay;


import javax.crypto.Cipher;
import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * RSA工具类
 *
 * @author zouhl
 */
public class RSAUtils {

    /**
     * 加密算法RSA
     */
    public static final String KEY_ALGORITHM = "RSA";
    /**
     * 加签算法SHA256WithRSA
     */
    public static final String SIGNATURE_ALGORITHM = "SHA256WithRSA";

    /**
     * 公钥格式：cer
     */
    public static final String PUBLIC_KEY_TYPE_CER = "cer";

    /**
     * 公钥格式：pem
     */
    public static final String PUBLIC_KEY_TYPE_PEM = "pem";
    /**
     * UTF-8
     */
    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private static KeyFactory keyFactory;

    static {
        try {
            keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        } catch (Exception e) {
            System.out.println("加载[{}]算法失败:" + KEY_ALGORITHM);
        }
    }

    /**
     * <p>
     * RSA公钥加密
     * </p>
     *
     * @param data      源数据
     * @param publicKey 公钥
     * @return
     * @throws Exception
     */
    public static String encryptByPublicKey(String data, PublicKey publicKey) throws Exception {
        // 对数据加密
        try {
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return Encodes.encodeBase64(cipher.doFinal(data.getBytes(DEFAULT_CHARSET)));
        } catch (Exception e) {
            System.out.println("使用公钥加密失败!");
            throw new Exception("使用公钥加密失败");
        }
    }

    /**
     * RSA解密
     *
     * @param
     * @param inputStr
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static String decryptByRSA(String inputStr, PrivateKey privateKey) throws Exception {
        return decryptByRSA(inputStr, privateKey, DEFAULT_CHARSET);
    }

    /**
     * RSA解密
     *
     * @param
     * @param inputStr
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static String decryptByRSA(String inputStr, PrivateKey privateKey, Charset charset) throws Exception {
        try {
            Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] output = cipher.doFinal(Encodes.decodeBase64(inputStr));
            return new String(output, charset);
        } catch (Exception e) {
            throw new Exception("RSA解密失败");
        }
    }

    /**
     * 读取私钥
     *
     * @param keyContent
     * @return
     * @throws Exception
     */
    public static PrivateKey readPrivateKey(String keyContent) throws Exception {
        try {
            byte[] keyBytes = Base64.getDecoder().decode(keyContent);
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
            RSAPrivateKey privateK = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
            return privateK;
        } catch (Exception e) {
            throw new Exception("读取私钥失败");
        }
    }

    /**
     * 获取公钥
     *
     * @param pemContent 私钥证书Base64密文
     * @return 私钥
     * @throws Exception 解析异常
     */
    public static PublicKey readPublicKey(String pemContent) throws Exception {
        try {
            byte[] encodeByte = Base64.getDecoder().decode(pemContent);
            PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodeByte));
            return publicKey;
        } catch (Exception e) {
            throw new Exception("读取公钥失败");
        }
    }

    /**
     * 用私钥对信息生成数字签名
     *
     * @param data       待签名数据
     * @param privateKey 私钥
     * @throws Exception
     */
    public static String sign(byte[] data, PrivateKey privateKey) throws Exception {
        return sign(data, privateKey, SIGNATURE_ALGORITHM);
    }

    /**
     * 用私钥对信息生成数字签名
     *
     * @param data       待签名数据
     * @param privateKey 私钥
     * @throws Exception
     */
    public static String sign(byte[] data, PrivateKey privateKey, String signAlgorithm) throws Exception {
        // 用私钥对信息生成数字签名
        try {
            Signature signature = Signature.getInstance(signAlgorithm);
            signature.initSign(privateKey);
            signature.update(data);
            return Encodes.encodeBase64(signature.sign());
        } catch (Exception e) {
            throw new Exception("签名失败");
        }
    }

    /**
     * 签名
     *
     * @param plainText  签名原文
     * @param algorithm  签名算法
     * @param privateKey 私钥
     * @return 签名结果(BASE64)
     * @throws Exception 签名异常
     */
    public static String sign(String plainText, String algorithm, PrivateKey privateKey) throws Exception {
        try {
            Signature sig = Signature.getInstance(algorithm);
            sig.initSign(privateKey);
            sig.update(plainText.getBytes());
            byte[] b = sig.sign();
            return Base64.getEncoder().encodeToString(b);
        } catch (NoSuchAlgorithmException e) {
            System.out.println("签名异常，不支持算法[{}]" + algorithm);
            throw new Exception(e);
        } catch (SignatureException e) {
            System.out.println("签名异常，异常原因[{}]" + e.getMessage());
            throw new Exception(e);
        } catch (InvalidKeyException e) {
            System.out.println("签名异常，无效密钥[{}]" + privateKey);
            throw new Exception(e);
        }
    }

    /**
     * 校验数字签名
     *
     * @param data   加密数据
     * @param pubKey 公钥
     * @param sign   数字签名
     * @return 校验成功返回true，失败返回false
     * @throws Exception
     */
    public static boolean verify(byte[] data, PublicKey pubKey, String sign) throws Exception {
        return verify(data, pubKey, sign, SIGNATURE_ALGORITHM);
    }

    /**
     * 校验数字签名
     *
     * @param data   加密数据
     * @param pubKey 公钥
     * @param sign   数字签名
     * @return 校验成功返回true，失败返回false
     * @throws Exception
     */
    public static boolean verify(byte[] data, PublicKey pubKey, String sign, String signAlgorithm) throws Exception {
        try {
            Signature signature = Signature.getInstance(signAlgorithm);
            signature.initVerify(pubKey);
            signature.update(data);
            // 验证签名是否正常
            return signature.verify(Encodes.decodeBase64(sign));
        } catch (Exception e) {
            throw new Exception("验签失败");
        }
    }

    /**
     * 获取公钥证书:cer,X509
     *
     * @param pemContent 公钥证书Base64密文
     * @return 公钥证书
     * @throws Exception 解析异常
     */
    public static X509Certificate loadCertificate(String pemContent) throws Exception {
        CertificateFactory factory = null;
        try {
            factory = CertificateFactory.getInstance("X.509");
            ByteArrayInputStream is = new ByteArrayInputStream(Base64.getDecoder().decode(pemContent));
            X509Certificate certificate = (X509Certificate) factory.generateCertificate(is);
            return certificate;
        } catch (CertificateException e) {
            throw new Exception("加载CER公钥失败");
        }
    }


    /**
     * 获取公钥
     *
     * @param pubContent 私钥证书Base64密文
     * @return 公钥
     * @throws Exception 解析异常
     */
    public static PublicKey readPublicKey(String pubContent, String keyType) throws Exception {
        PublicKey pubKey = null;
        if (PUBLIC_KEY_TYPE_CER.equals(keyType)) {
            CertificateFactory factory = null;
            factory = CertificateFactory.getInstance("X.509");
            ByteArrayInputStream is = new ByteArrayInputStream(Base64.getDecoder().decode(pubContent));
            X509Certificate certificate = (X509Certificate) factory.generateCertificate(is);
            pubKey = certificate.getPublicKey();
        } else if (PUBLIC_KEY_TYPE_PEM.equals(keyType)) {
            byte[] encodeByte = Base64.getDecoder().decode(pubContent);
            KeyFactory fact = KeyFactory.getInstance("RSA");
            pubKey = fact.generatePublic(new X509EncodedKeySpec(encodeByte));
        }
        return pubKey;
    }
}